﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl.Statements;

#endregion

namespace Weazel.Badger.Vhdl
{
  public class Entity
  {
    /// <summary>
    /// The name of this entity
    /// </summary>
		private string name;
		public string Name
		{
			get { return name; }	
		}
    
    /// <summary>
    /// Items to declare needing declaration (signals,
    /// enumeration types and so on)
    /// </summary>
		private List<IDeclarativeItem> declarativeItems = new List<IDeclarativeItem>();
		public List<IDeclarativeItem> DeclarativeItems
		{
			get { return declarativeItems; }
		}

    private List<PortMap> portMaps = new List<PortMap>();

    /// <summary>
    /// The processes of this entity
    /// </summary>
    private List<Process> processes = new List<Process>();
    public List<Process> Processes
    {
      get
      {
        return processes;
      }

      set
      {
        processes = value;
      }
    }

		private StatementCollection statements = new StatementCollection();
		public StatementCollection Statements
		{
			get { return statements; }
		}

    /// <summary>
    /// List of signals. This collection also contains all
    /// ports.
    /// </summary>
    private readonly List<Signal> signals = new List<Signal>();
    public List<Signal> Signals
    {
      get
      {
        return signals;
      }
    }

    /// <summary>
    /// List of ports
    /// </summary>
    private readonly List<Port> ports = new List<Port>();
    public List<Port> Ports
    {
      get
      {
        return ports;
      }
    }

		private Dictionary<string, Vhdl.ComponentDeclaration> children =
			new Dictionary<string, Vhdl.ComponentDeclaration>();
		public Dictionary<string, Vhdl.ComponentDeclaration> Children
		{
			get
			{
				return children;
			}
		}

		/// <summary>
		/// Text will be printed as the very first thing 
		/// in the entity header. Used for outputting generics
		/// Note: generics are not supported otherwise.
		/// </summary>
		private string generic;
		public string Generic
		{
			get { return generic; }
			set { generic = value; }
		}

    /// <summary>
    /// Given the name of a signal this method
    /// return that signal or null if it does not
    /// exist.
    /// </summary>
    /// <param name="signalName">name of signal to lookup</param>
    /// <returns>the desired signal or null if not found</returns>
    public Signal FindSignal(string signalName)
    {
      foreach (Signal s in signals)
        if (s.Name == signalName)
          return s;

      return null;
    }

    /// <summary>
    /// Add a declarative item to this entity
    /// </summary>
    /// <param name="declarativeItem">Item to add</param>
    public void AddDeclarativeItem(IDeclarativeItem declarativeItem)
    {
      // only add it not already in list
      if (!declarativeItems.Contains(declarativeItem))
        declarativeItems.Add(declarativeItem);
    }

    /// <summary>
    /// Add a new process to this entity
    /// </summary>
    /// <param name="process">Process to add</param>
    public void AddProcess(Process process)
    {
      if (!processes.Contains(process))
        processes.Add(process);
    }

    /// <summary>
    /// Add a new signal (or port)
    /// </summary>
    /// <param name="signal">signal to add</param>
    public void AddSignal(Signal signal)
    {
      if (signal is Port)
      {
        if (!ports.Contains((Port)signal))
          ports.Add((Port)signal);
      }
      if (!signals.Contains(signal))
      {
        signals.Add(signal);
      }
    }

    /// <summary>
    /// Add a new port
    /// </summary>
    /// <param name="port"></param>
    public virtual void AddPort(Port port)
    {
      AddSignal(port);
    }

    public void AddPortMap(PortMap portMap)
    {
      portMaps.Add(portMap);
    }

    public Entity(string name)
    {
      this.name = name;
    }
    
    /// <summary>
    /// Resolve all expressions in this entity. 
    /// </summary>
    public virtual void Resolve()
    {
      foreach (Process p in processes)
        p.Resolve(this);

			statements.Resolve(this, null, null);
    }

    public void Verify(ref List<VerifyMessage> messages)
    {
      foreach (Process p in processes)
        p.Verify(ref messages);
    }
  
    public Entity[] GetParentEntities()
    {
      return new Entity[] { this };
    }

		/// <summary>
		/// Returns a "ComponentDeclaration" object that can be used
		/// to instantiate an instance of this entity within another.
		/// </summary>
		/// <returns>Declartion of this entity</returns>
		public Vhdl.ComponentDeclaration GetDeclaration()
		{
			Vhdl.ComponentDeclaration component =
				new Weazel.Badger.Vhdl.ComponentDeclaration(Name, this);

			foreach (Vhdl.Port port in Ports)
			{
				if (port.PortDirection == Weazel.Badger.Vhdl.PortDirection.In)
					component.AddIn(port.Name, port.Type, port.DeclarationPosition);
				else if (port.PortDirection == Weazel.Badger.Vhdl.PortDirection.Out)
					component.AddOut(port.Name, port.Type, port.DeclarationPosition);
				else
					throw new NotSupportedException("Inout port not supported");
			}

			return component;
		}

    /// <summary>
    /// Output the port and all content to a stream
    /// </summary>
    /// <param name="writer"></param>
    public void Write(System.CodeDom.Compiler.IndentedTextWriter writer)
    {
      writer.WriteLine(String.Format("Entity {0} is ", name));
      writer.Indent++;

			if (generic != null)
			{
				writer.WriteLine(generic);
			}

      if (ports.Count > 0)
      {
        writer.WriteLine("port (");
        writer.Indent++;

        for (int i = 0; i < ports.Count; i++)
        {
          // no terminating ';' on the last port declaration
          if (i == ports.Count - 1)
            writer.WriteLine(ports[i].GetDeclaration());
          else
            writer.WriteLine(ports[i].GetDeclaration() + ";");
        }

        writer.Indent--;
        writer.WriteLine(");");
      }
      else
      {
        writer.WriteLine("-- no ports declared for this Entity");
        writer.Indent--;
      }

      writer.Indent--;
      writer.WriteLine(String.Format("end {0};", name));

      writer.WriteLine();

      writer.WriteLine("architecture REGISTER_TRANSFER_LEVEL of {0} is", name);
      writer.WriteLine();
      writer.Indent++;
      
      foreach (IDeclarativeItem declarativeItem in declarativeItems)
      {
        declarativeItem.Write(writer);
      }

      writer.WriteLine();

      writer.WriteLine("begin");
      writer.Indent++;

      if (portMaps.Count > 0)
      {
        writer.WriteLine("-- ----------------------------------------");
        writer.WriteLine("--  Port maps");
        writer.WriteLine("-- ----------------------------------------");

        foreach (PortMap portMap in portMaps)
        {
          portMap.Write(writer);
          writer.WriteLine();
        }

        writer.WriteLine();
      }

      foreach (Process process in processes)
      {
        process.Write(writer);
        writer.WriteLine();
      }
			
			statements.Write(writer);

      writer.Indent--;
      writer.Indent--;
      writer.WriteLine("end architecture REGISTER_TRANSFER_LEVEL;");
    }
  }
}
